from typing import TYPE_CHECKING, List, Optional, Type

from ..config import Config
from .irbasemapping import IRBaseMapping
from .irhttpmapping import IRHTTPMapping
from .irtcpmapping import IRTCPMapping

if TYPE_CHECKING:
    from .ir import IR  # pragma: no cover


def unique_mapping_name(aconf: Config, name: str) -> str:
    http_mappings = aconf.get_config("mappings") or {}
    tcp_mappings = aconf.get_config("tcpmappings") or {}

    basename = name
    counter = 0

    while name in http_mappings or name in tcp_mappings:
        name = f"{basename}-{counter}"
        counter += 1

    return name


class MappingFactory:
    @classmethod
    def load_all(cls, ir: "IR", aconf: Config) -> None:
        cls.load_config(ir, aconf, "Mapping", "mappings", IRHTTPMapping)
        cls.load_config(ir, aconf, "TCPMapping", "tcpmappings", IRTCPMapping)

    @classmethod
    def load_config(
        cls,
        ir: "IR",
        aconf: Config,
        kind: str,
        config_name: str,
        mapping_class: Type[IRBaseMapping],
    ) -> None:
        config_info = aconf.get_config(config_name)

        if not config_info:
            return

        assert len(config_info) > 0  # really rank paranoia on my part...

        live_mappings: List[IRBaseMapping] = []

        for config in config_info.values():
            ir.logger.debug("IR: MappingFactory checking %s" % repr(config))

            # Is this mapping already in the cache?
            key = IRBaseMapping.make_cache_key(kind, config.name, config.namespace)

            mapping: Optional[IRBaseMapping] = None
            cached_mapping = ir.cache_fetch(key)

            if cached_mapping is None:
                # Cache miss: synthesize a new Mapping.
                ir.logger.debug(f"IR: synthesizing Mapping for {config.name}")
                mapping = mapping_class(ir, aconf, **config)
            else:
                # Cache hit. We know a priori that anything in the cache under a Mapping
                # key must be an IRBaseMapping, but let's assert that rather than casting.
                assert isinstance(cached_mapping, IRBaseMapping)
                mapping = cached_mapping

            if mapping:
                ir.logger.debug(f"IR: live Mapping for {config.name}")
                live_mappings.append(mapping)

        ir.logger.debug("IR: MappingFactory checking invalidations")

        for mapping in live_mappings:
            if mapping.cache_key in ir.invalidate_groups_for:
                group_key = mapping.group_class().key_for_id(mapping.group_id)
                ir.logger.debug(
                    "IR: MappingFactory invalidating %s for %s", group_key, mapping.name
                )
                ir.cache.invalidate(group_key)

        ir.logger.debug("IR: MappingFactory adding live mappings")

        for mapping in live_mappings:
            ir.logger.debug("IR: MappingFactory adding %s" % mapping.name)
            ir.add_mapping(aconf, mapping)

        ir.cache.dump("MappingFactory")

    @classmethod
    def finalize(cls, ir: "IR", aconf: Config) -> None:
        # OK. We've created whatever IRMappings we need. Time to create the clusters
        # they need.

        ir.logger.debug("IR: MappingFactory finalizing")

        for group in ir.groups.values():
            ir.logger.debug("IR: MappingFactory finalizing group %s", group.group_id)
            group.finalize(ir, aconf)
            ir.logger.debug("IR: MappingFactory finalized group %s", group.group_id)

        ir.logger.debug("IR: MappingFactory finalized")
